﻿
package extremefx.storage {
	import extremefx.IEquatable;	
	import extremefx.IDisposable;
	import extremefx.events.EventHandler;
	import extremefx.events.MessageEventArgs;
	import extremefx.events.StatusEventArgs;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLRequest;
	import flash.utils.clearInterval;
	import flash.utils.getTimer;
	import flash.utils.setInterval;	

	/**
	 * @author Marcelo Volmaro
	 */
	public class QeuedItem implements IDisposable, IEquatable {
		protected var _loadUrl:URLRequest;
		protected var _cnx:Object;
		
		private var _url:URLRequest;
		private var _loading:Boolean;
		private var _loaded:Boolean;
		private var _retries:uint;
		private var _startTime:uint;
		private var _connTime:uint;
		private var _lastDataTime:uint;
		private var _doneTime:uint;
		private var _toInterval:uint;
	
		protected var _bytesLoaded:uint;
		protected var _bytesTotal:uint;
		private var _percentLoaded:Number;
		private var _httpStatus:Number;
		private var _disposing:Boolean;
		private var _handler:EventDispatcher;
		
		/**
		 * Appends a random string at the end of the request to prevent caching
		 */
		public var noCache:Boolean;
		
		/**
		 * Maximum number of retries for this Item
		 */
		public var maxRetries:uint;
		
		/**
		 * Maximum ammount of time the class should wait until it aborts the loading.
		 */
		public var timeOut:uint;
		
		public const userData:Object = {};

		/**
		 * Provides a way to abstract loading of resources. Can be used alone or in conjuction with <code>LoadQeue</code>
		 * @param pUrl an URLRequest used to read the resource
		 * @param pType the <code>QeuedItemType</type> of the resource. If nothing is passed (null or empty string) the class will try to guess the type by its extension.
		 * @param pContext the Context in wich the resource will be loaded.
		 */
		public function QeuedItem(pUrl:*, pContext:Object){
			var url:URLRequest;
			
			if (pUrl is String) {
				url = new URLRequest(String(pUrl));
				
			} else if (pUrl is URLRequest) {
				url = URLRequest(pUrl);
				
			} else {
				throw new ArgumentError("Invalid parameter, only string or urlrequest allowed");
			}
			
			_url = url;
			_cnx = pContext;
			noCache = false;
			_loaded = _loading = false;
			_bytesLoaded = _bytesTotal = _percentLoaded = _httpStatus = _retries = 0;
			maxRetries = 3;
			timeOut = 60;
		}
		
		/**
		 * Starts loading the resource.
		 */
		public function start():Boolean {
			if (_loaded) return false;
			if (_loading) stop();
			
			var d:URLRequest = _url;
			
			if (noCache){
				var rnd:String = "noCache="+int(Math.random()  * 1000 * getTimer());
				if (d.url.indexOf("?") == -1){
					d.url += "?"+rnd;
					
				} else {
					d.url += "&"+rnd;
				}
            }
            
            _loadUrl = d;
			_loading = true;
			_startTime = _lastDataTime = getTimer();
			_toInterval = setInterval(_checkTimeOut, uint(timeOut * 1000 / 2) || 10000);
			return true;
		}
		
		/**
		 * Stops loading the resource.
		 */
		public function stop():Boolean {
			if (_loaded) return false;
			_loading = false;
			clearInterval(_toInterval);
			_removeHandlers();
			return true;
		}
		
        /**
         * Gets the number of bytes loaded from the resource.
         */
        public function get bytesLoaded():uint{
        	return _bytesLoaded;
        }
        
        /**
         * Gets the total number of bytes from the resource.
         */
        public function get bytesTotal():uint{
        	return _bytesTotal;
        }
        
        /**
         * Gets the percentaje of the resource that has been loaded
         */
        public function get percentLoaded():uint{
        	return _percentLoaded;
        }
        
        /**
         * Gets the http status of the last operation. Only available after an <code>onHttpStatus</code> event fires.
         */
        public function get httpStatus():Number{
        	return _httpStatus;
        }
        
        private function _checkTimeOut():void {
        	if ((_lastDataTime + timeOut) < getTimer()) return;
        	
        	if (maxRetries < ++_retries){
        		if (_eOnTimeOut) _eOnTimeOut.fire();
				
			} else {
				start();
			} 
		}

		private function _onHTTPStatus(pE:HTTPStatusEvent):void {
			_httpStatus = pE.status;
			if (_eOnHttpStatus) _eOnHttpStatus.fire(new StatusEventArgs("", "", pE.status));
		}
		
		private function _onSecError(pE:SecurityErrorEvent):void {
			if (_eOnSecurityError) _eOnSecurityError.fire(new MessageEventArgs(pE.text));
		}

		protected function _onIOError(pE:IOErrorEvent):void {
			if (maxRetries < ++_retries){
				if (_eOnIoError) _eOnIoError.fire(new MessageEventArgs(pE.text));
				
			} else {
				start();
			}
		}

		protected function _onComplete(pE:Event):void {
			_loaded = true;
			_doneTime = getTimer();
			clearInterval(_toInterval);
			_onLoadComplete();
			
			_removeHandlers();
			if (_eOnComplete) _eOnComplete.fire();
		}
		
		public function loaded():Boolean {
			return _loaded;
		}
		
		protected function _onLoadComplete():void{}

		protected function _onProgress(pE:ProgressEvent):void {
			_bytesLoaded = pE.bytesLoaded;
			_bytesTotal = pE.bytesTotal;
			_percentLoaded = _bytesLoaded / _bytesTotal;
			_lastDataTime = getTimer();
			
			if (_eOnProgress) _eOnProgress.fire();
		}

		protected function _onOpen(pE:Event):void {
			_connTime = _lastDataTime = getTimer();
			if (_eOnOpen) _eOnOpen.fire();
		}
		
		protected function _addHandlers(pHandler:EventDispatcher):void {
			_handler = pHandler;
			pHandler.addEventListener(Event.OPEN, _onOpen, false, 0, true);
			pHandler.addEventListener(ProgressEvent.PROGRESS, _onProgress, false, 0, true);
			pHandler.addEventListener(Event.COMPLETE, _onComplete, false, 0, true);
			pHandler.addEventListener(IOErrorEvent.IO_ERROR , _onIOError, false, 0, true);
			pHandler.addEventListener(SecurityErrorEvent.SECURITY_ERROR , _onSecError, false, 0, true);
			pHandler.addEventListener(HTTPStatusEvent.HTTP_STATUS , _onHTTPStatus, false, 0, true);
		}
		
		protected function _removeHandlers():void {
			if (_handler == null) return;
			
			_handler.removeEventListener(Event.OPEN, _onOpen);
			_handler.removeEventListener(ProgressEvent.PROGRESS, _onProgress);
			_handler.removeEventListener(Event.COMPLETE, _onComplete);
			_handler.removeEventListener(IOErrorEvent.IO_ERROR , _onIOError);
			_handler.removeEventListener(HTTPStatusEvent.HTTP_STATUS , _onHTTPStatus);
			_handler = null;
		}
		
		public function dispose():void {
			if (_disposing) return;
			_disposing = true;
			
			_removeHandlers();
			_eOnOpen = _eOnProgress = _eOnComplete = _eOnIoError = _eOnHttpStatus = 
			_eOnSecurityError = _eOnTimeOut = null;
		}
		
		private var _eOnOpen:EventHandler;
		/**
		 * Event. Fires when the class opens the resource from the server.
		 */
		public function get onOpen():EventHandler{
			if (_eOnOpen == null) _eOnOpen = new EventHandler(this);
			return _eOnOpen;
		}
		
		private var _eOnProgress:EventHandler;
		/**
		 * Event. Fires every time the class gets some resource data from the server.
		 */
		public function get onProgress():EventHandler{
			if (_eOnProgress == null) _eOnProgress = new EventHandler(this);
			return _eOnProgress;
		}
		
		private var _eOnComplete:EventHandler;
		/**
		 * Event. Fires when the resource has been loaded.
		 */
		public function get onComplete():EventHandler{
			if (_eOnComplete == null) _eOnComplete = new EventHandler(this);
			return _eOnComplete;
		}
		
		private var _eOnIoError:EventHandler;
		/**
		 * Event. Fires if an IOError occurred.
		 */
		public function get onIoError():EventHandler{
			if (_eOnIoError == null) _eOnIoError = new EventHandler(this);
			return _eOnIoError;
		}
		
		private var _eOnHttpStatus:EventHandler;
		/**
		 * Event. Fires when an HTTP Status is received.
		 */
		public function get onHttpStatus():EventHandler{
			if (_eOnHttpStatus == null) _eOnHttpStatus = new EventHandler(this);
			return _eOnHttpStatus;
		}
		
		private var _eOnSecurityError:EventHandler;
		/**
		 * Event. Fires on when Security Errors happens.
		 */
		public function get onSecurityError():EventHandler{
			if (_eOnSecurityError == null) _eOnSecurityError = new EventHandler(this);
			return _eOnSecurityError;
		}
		
		private var _eOnTimeOut:EventHandler;
		/**
		 * Event. Fires when the maximum waiting time without receiving data period has been reached.
		 */
		public function get onTimeOut():EventHandler{
			if (_eOnTimeOut == null) _eOnTimeOut = new EventHandler(this);
			return _eOnTimeOut;
		}
		
		public function equals(pObj:*):Boolean {
			if (!(pObj is QeuedItem)) return false;
			
			return _url == (pObj as QeuedItem)._url;
		}
	}
}
